// OpenArchive.cpp

#include "StdAfx.h"

#include "Common/FileSystem/Wildcard.h"

#include "Common/FileSystem/FileDir.h"
#include "Common/MS/PropVariant.h"

#include "Common/Stream/FileStreams.h"
#include "Common/Stream/StreamUtils.h"

#include "Common/FileSystem/DefaultName.h"
#include "OpenArchive.h"

#include <deque>
#include <vector>

namespace Common
{
namespace Archive
{

	
// Static-SFX (for Linux) can be big.
const UINT64 kMaxCheckStartPosition = 1 << 22;

HRESULT GetArchiveItemBoolProp(IInArchive *archive, UINT32 index, PROPID propID, bool &result)
{
  CComPropVariant prop;
  result = false;
  HRESULT hRes = archive->GetProperty(index, propID, &prop);
  if(S_OK != hRes)
		return hRes;
  if (prop.vt == VT_BOOL)
	result = VARIANT_BOOLToBool(prop.boolVal);
  else if (prop.vt != VT_EMPTY)
	return E_FAIL;
  return S_OK;
}

HRESULT IsArchiveItemFolder(IInArchive *archive, UINT32 index, bool &result)
{
  return GetArchiveItemBoolProp(archive, index, kpidIsDir, result);
}

HRESULT CArc::GetItemPath(UINT32 index, CString &result) const
{
  HRESULT hRes(S_OK);
  {
	CComPropVariant prop;
	hRes = Archive->GetProperty(index, kpidPath, &prop);
	if(S_OK != hRes)
		return hRes;
	if (prop.vt == VT_BSTR)
	  result = prop.bstrVal;
	else if (prop.vt == VT_EMPTY)
	  result.Empty();
	else
	  return E_FAIL;
  }
  if (result.IsEmpty())
  {
	result = DefaultName;
	CComPropVariant prop;
	hRes = Archive->GetProperty(index, kpidExtension, &prop);
	if(S_OK != hRes)
		return hRes;
	if (prop.vt == VT_BSTR)
	{
	  result += _T('.');
	  result += prop.bstrVal;
	}
	else if (prop.vt != VT_EMPTY)
	  return E_FAIL;
  }
  return S_OK;
}

HRESULT CArc::GetItemMTime(UINT32 index, FILETIME &ft, bool &defined) const
{
  CComPropVariant prop;
  defined = false;
  ft.dwHighDateTime = ft.dwLowDateTime = 0;
  HRESULT hRes = Archive->GetProperty(index, kpidMTime, &prop);
  if(S_OK != hRes)
		return hRes;
  if (prop.vt == VT_FILETIME)
  {
	ft = prop.filetime;
	defined = true;
  }
  else if (prop.vt != VT_EMPTY)
	return E_FAIL;
  else if (MTimeDefined)
  {
	ft = MTime;
	defined = true;
  }
  return S_OK;
}

#ifndef _SFX
static inline bool TestSignature(const BYTE *p1, const BYTE *p2, size_t size)
{
  for (size_t i = 0; i < size; i++)
	if (p1[i] != p2[i])
	  return false;
  return true;
}
#endif

static const int kNumHashBytes = 2;
#define HASH_VAL(buf, pos) ((buf)[pos] | ((UINT32)(buf)[pos + 1] << 8))



HRESULT CArc::OpenStream(
	CCodecs *codecs,
	int formatIndex,
	IInStream *stream,
	ISequentialInStream *seqStream,
	IArchiveOpenCallback *callback)
{
  Archive.Release();
  ErrorMessage.Empty();
  const CString fileName = Common::FileSystem::ExtractFileNameFromPath(Path);
  CString extension;
  {
	int dotPos = fileName.ReverseFind(_T('.'));
	if (dotPos >= 0)
	  extension = fileName.Mid(dotPos + 1);
  }

  HRESULT hRes(S_OK);

  std::deque<size_t> orderIndices;
  if (formatIndex != -1)
	  orderIndices.push_back(formatIndex);
  else
  {

  size_t i;
  size_t numFinded = 0;
  for (i = 0; i < codecs->Formats.size(); i++)
	if (codecs->Formats[i].FindExtension(extension) != -1)
	  //insert found formats first
		  orderIndices.insert(orderIndices.begin() + numFinded++, i);
	else
		orderIndices.push_back(i);
  
  if (!stream)
  {
	if (numFinded != 1)
	  return E_NOTIMPL;
	orderIndices.erase(orderIndices.begin() + 1);
  }

  
  #ifndef _SFX
  if (orderIndices.size() >= 2 && (numFinded == 0 || extension.CompareNoCase(_T("exe")) == 0))
  {
	std::deque<size_t> orderIndices2;
	//CByteBuffer byteBuffer;
	ByteBuffer byteBuffer;
	const size_t kBufferSize = (1 << 21);
	//byteBuffer.SetCapacity(kBufferSize);
	byteBuffer.resize(kBufferSize);
	hRes = stream->Seek(0, STREAM_SEEK_SET, NULL);
	if(S_OK != hRes)
		return hRes;
	size_t processedSize = kBufferSize;
	hRes = ReadStream(stream, &byteBuffer[0], &processedSize);
	if(S_OK != hRes)
		return hRes;
	if (processedSize == 0)
	  return S_FALSE;

	const BYTE *buf = &byteBuffer[0];
	//CByteBuffer hashBuffer;
	ByteBuffer hashBuffer;
	const UINT32 kNumVals = 1 << (kNumHashBytes * 8);
	//hashBuffer.SetCapacity(kNumVals);
	hashBuffer.resize(kNumVals);
	BYTE *hash = &hashBuffer[0];
	memset(hash, 0xFF, kNumVals);
	BYTE prevs[256];
	if (orderIndices.size() >= 256)
	  return S_FALSE;
	size_t i;
	for (i = 0; i < orderIndices.size(); i++)
	{
	  const CArcInfoEx &ai = codecs->Formats[orderIndices[i]];
	  //const CByteBuffer &sig = ai.StartSignature;
	  const ByteBuffer &sig = ai.StartSignature;
	  //if (sig.GetCapacity() < kNumHashBytes)
	  if (sig.size() < kNumHashBytes)
		continue;
	  UINT32 v = HASH_VAL(sig, 0);
	  prevs[i] = hash[v];
	  hash[v] = (BYTE)i;
	}

	processedSize -= (kNumHashBytes - 1);
	for (UINT32 pos = 0; pos < processedSize; pos++)
	{
	  for (; pos < processedSize && hash[HASH_VAL(buf, pos)] == 0xFF; pos++);
	  if (pos == processedSize)
		break;
	  UINT32 v = HASH_VAL(buf, pos);
	  BYTE *ptr = &hash[v];
	  int i = *ptr;
	  do
	  {
		size_t index = orderIndices[i];
		const CArcInfoEx &ai = codecs->Formats[index];
		//const CByteBuffer &sig = ai.StartSignature;
		const ByteBuffer &sig = ai.StartSignature;
		//if (sig.GetCapacity() != 0 && pos + sig.GetCapacity() <= processedSize + (kNumHashBytes - 1) &&
		//	TestSignature(buf + pos, sig, sig.GetCapacity()))
		if (sig.size() != 0 && pos + sig.size() <= processedSize + (kNumHashBytes - 1) &&
			TestSignature(buf + pos, &sig[0], sig.size()))
		{
			orderIndices2.push_back(index);
			orderIndices[i] = -1;
			*ptr = prevs[i];
		}
		else
		  ptr = &prevs[i];
		i = *ptr;
	  }
	  while (i != 0xFF);
	}
	
	for (size_t i = 0; i < orderIndices.size(); i++)
	{
	  size_t val = orderIndices[i];
	  if (val != -1)
		  orderIndices2.push_back(val);
	}
	orderIndices = orderIndices2;
  }
  else if (extension == _T("000") || extension == _T("001"))
  {
	//CByteBuffer byteBuffer;
	  ByteBuffer byteBuffer;
	const size_t kBufferSize = (1 << 10);
	//byteBuffer.SetCapacity(kBufferSize);
	byteBuffer.resize(kBufferSize);
	BYTE *buffer = &byteBuffer[0];
	hRes =  stream->Seek(0, STREAM_SEEK_SET, NULL);
	if(S_OK != hRes)
		return hRes;
	size_t processedSize = kBufferSize;
	hRes = ReadStream(stream, buffer, &processedSize);
	if(S_OK != hRes)
		return hRes;
	if (processedSize >= 16)
	{
	  BYTE kRarHeader[] = {0x52 , 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00};
	  if (TestSignature(buffer, kRarHeader, 7) && buffer[9] == 0x73 && (buffer[10] & 1) != 0)
	  {
		for (size_t i = 0; i < orderIndices.size(); i++)
		{
		  size_t index = orderIndices[i];
		  const CArcInfoEx &ai = codecs->Formats[index];
		  if (ai.Name.CompareNoCase(_T("rar")) != 0)
			continue;
		  orderIndices.erase(orderIndices.begin()  + i--);
		  orderIndices.push_front(index);
		  break;
		}
	  }
	}
  }
  if (orderIndices.size() >= 2)
  {
	size_t isoIndex = codecs->FindFormatForArchiveType(_T("iso"));
	size_t udfIndex = codecs->FindFormatForArchiveType(_T("udf"));
	size_t iIso = -1;
	size_t iUdf = -1;
	for (size_t i = 0; i < orderIndices.size(); i++)
	{
	  if (orderIndices[i] == isoIndex)
		  iIso = i;
	  if (orderIndices[i] == udfIndex) 
		  iUdf = i;
	}
	if (iUdf > iIso && iIso != -1)
	{
	  orderIndices[iUdf] = isoIndex;
	  orderIndices[iIso] = udfIndex;
	}
  }

  #endif
  }

  for (size_t i = 0; i < orderIndices.size(); i++)
  {
	if (stream)
	{
	  hRes = stream->Seek(0, STREAM_SEEK_SET, NULL);
	  if(S_OK != hRes)
		return hRes;
	}
	CComPtr<IInArchive> archive;

	FormatIndex = orderIndices[i];
	hRes = codecs->CreateInArchive(FormatIndex, &archive);
	if(S_OK != hRes)
		return hRes;
	if (!archive)
	  continue;

	#ifdef EXTERNAL_CODECS
	{
	  CComPtr<ISetCompressCodecsInfo> setCompressCodecsInfo;
	  archive.QueryInterface(&setCompressCodecsInfo);
	  if (setCompressCodecsInfo)
	  {
		hRes = setCompressCodecsInfo->SetCompressCodecsInfo(codecs);
		if(S_OK != hRes)
		return hRes;
	  }
	}
	#endif

	// OutputDebugStringW(codecs->Formats[FormatIndex].Name);

	if (stream)
	  hRes = archive->Open(stream, &kMaxCheckStartPosition, callback);
	else
	{
	  CComPtr<IArchiveOpenSeq> openSeq;
	  archive.QueryInterface(&openSeq);
	  if (!openSeq)
		return E_NOTIMPL;
	  hRes = openSeq->OpenSeq(seqStream);
	}

	if (S_FALSE == hRes)
	  continue;

	if(S_OK != hRes)
		return hRes;

	{
	  CComPropVariant prop;
	  archive->GetArchiveProperty(kpidError, &prop);
	  if (prop.vt != VT_EMPTY)
		ErrorMessage = (prop.vt == VT_BSTR) ? prop.bstrVal : _T("Unknown error");
	}
	
	Archive = archive;
	const CArcInfoEx &format = codecs->Formats[FormatIndex];
	if (format.Exts.size() == 0)
	  DefaultName = FileSystem::GetDefaultName2(fileName, _T(""), _T(""));
	else
	{
	  int subExtIndex = format.FindExtension(extension);
	  if (subExtIndex < 0)
		subExtIndex = 0;
	  const CArcExtInfo &extInfo = format.Exts[subExtIndex];
	  DefaultName = FileSystem::GetDefaultName2(fileName, extInfo.Ext, extInfo.AddExt);
	}
	return S_OK;
  }
  return S_FALSE;
}

HRESULT CArc::OpenStreamOrFile(
	CCodecs *codecs,
	int formatIndex,
	bool stdInMode,
	IInStream *stream,
	IArchiveOpenCallback *callback)
{
  CComPtr<IInStream> fileStream;
  CComPtr<ISequentialInStream> seqStream;
  if (stdInMode)
  {
	CStdInFileStream * stream = new CComObject<CStdInFileStream>;
	stream->FinalConstruct();
	seqStream = stream;
	
  }
  else if (!stream)
  {
	CInFileStream *fileStreamSpec = new CComObject<CInFileStream>;
	fileStreamSpec->FinalConstruct();
	fileStream = fileStreamSpec;
	if (!fileStreamSpec->Open(Path))
	  return GetLastError();
	stream = fileStream;
  }

  /*
  if (callback)
  {
	UINT64 fileSize;
	RINOK(stream->Seek(0, STREAM_SEEK_END, &fileSize));
	RINOK(callback->SetTotal(NULL, &fileSize))
  }
  */

  return OpenStream(codecs, formatIndex, stream, seqStream, callback);
}

HRESULT CArchiveLink::Close()
{
  HRESULT hRes (S_OK);
  for (auto it = Arcs.rbegin();  it != Arcs.rend(); ++it)
  {
	hRes = (*it)->Archive->Close();
	if(S_OK != hRes)
		return hRes;
  }
  IsOpen = false;
  return S_OK;
}

void CArchiveLink::Release()
{
  Arcs.clear();
}

HRESULT CArchiveLink::Open(
	CCodecs *codecs,
	const std::vector<int> &formatIndices,
	bool stdInMode,
	IInStream *stream,
	const CString &filePath,
	IArchiveOpenCallback *callback)
{
  Release();
  if (formatIndices.size() >= 32)
	return E_NOTIMPL;
  
  HRESULT resSpec;

  HRESULT hRes (S_OK);

  for (;;)
  {
	resSpec = S_OK;
	size_t formatIndex = -1;
	if (formatIndices.size() >= 1)
	{
	  if (Arcs.size() >= formatIndices.size())
		break;
	  formatIndex = formatIndices[formatIndices.size() - Arcs.size() - 1];
	}
	else if (Arcs.size() >= 32)
	  break;

	if (Arcs.empty())
	{
		std::shared_ptr<CArc> arc = std::make_shared<CArc>();
	  arc->Path = filePath;
	  arc->SubfileIndex = (UINT32)(INT32)-1;
	  hRes = arc->OpenStreamOrFile(codecs, formatIndex, stdInMode, stream, callback);
	  if(S_OK != hRes)
		return hRes;
	  Arcs.push_back(arc);
	  continue;
	}
	
	const CArc * const pArc = Arcs.back().get();
	
	resSpec = (formatIndices.size() == 0 ? S_OK : E_NOTIMPL);
	
	UINT32 mainSubfile;
	{
	  CComPropVariant prop;
	  hRes = pArc->Archive->GetArchiveProperty(kpidMainSubfile, &prop);
	  if(S_OK != hRes)
		return hRes;
	  if (prop.vt == VT_UI4)
		mainSubfile = prop.ulVal;
	  else
		break;
	  UINT32 numItems;
	  hRes = pArc->Archive->GetNumberOfItems(&numItems);
	  if(S_OK != hRes)
		return hRes;
	  if (mainSubfile >= numItems)
		break;
	}

  
	CComPtr<IInArchiveGetStream> getStream;
	if (pArc->Archive.QueryInterface(&getStream) != S_OK || !getStream)
	  break;
	
	CComPtr<ISequentialInStream> subSeqStream;
	if (getStream->GetStream(mainSubfile, &subSeqStream) != S_OK || !subSeqStream)
	  break;
	
	CComPtr<IInStream> subStream;
	if (subSeqStream.QueryInterface(&subStream) != S_OK || !subStream)
	  break;
	
	std::shared_ptr<CArc> arc = std::make_shared<CArc>();
	hRes = pArc->GetItemPath(mainSubfile, arc->Path);
	if(S_OK != hRes)
		return hRes;
	
	CComPtr<IArchiveOpenSetSubArchiveName> setSubArchiveName;
	callback->QueryInterface(__uuidof(IArchiveOpenSetSubArchiveName), (void **)&setSubArchiveName);
	if (setSubArchiveName)
	  setSubArchiveName->SetSubArchiveName(arc->Path);
	
	arc->SubfileIndex = mainSubfile;
	hRes = arc->OpenStream(codecs, formatIndex, subStream, NULL, callback);
	resSpec = (formatIndices.size() == 0 ? S_OK : S_FALSE);
	if (S_FALSE == hRes)
	  break;
	if(S_OK != hRes)
		return hRes;
	hRes = pArc->GetItemMTime(mainSubfile, arc->MTime, arc->MTimeDefined);
	if(S_OK != hRes)
		return hRes;
	Arcs.push_back(arc);
  }
  IsOpen = !Arcs.empty();
  return S_OK;
}

static void SetCallback(const CString &filePath,
	IOpenCallbackUI *callbackUI,
	IArchiveOpenCallback *reOpenCallback,
	CComPtr<IArchiveOpenCallback> &callback)
{
  COpenCallbackImp *openCallbackSpec = new CComObject<COpenCallbackImp>;
  openCallbackSpec->FinalConstruct();
  callback = openCallbackSpec;
  openCallbackSpec->Callback = callbackUI;
  openCallbackSpec->ReOpenCallback = reOpenCallback;

  CString fullName;
  int fileNamePartStartIndex;
  Common::FileSystem::GetFullPathName(filePath, fullName, fileNamePartStartIndex);
  openCallbackSpec->Init(
	  fullName.Left(fileNamePartStartIndex),
	  fullName.Mid(fileNamePartStartIndex));
}

HRESULT CArchiveLink::Open2(CCodecs *codecs,
	const std::vector<int> &formatIndices,
	bool stdInMode,
	IInStream *stream,
	const CString &filePath,
	IOpenCallbackUI *callbackUI)
{
  VolumesSize = 0;
  COpenCallbackImp *openCallbackSpec = new CComObject<COpenCallbackImp>;
  openCallbackSpec->FinalConstruct();
  CComPtr<IArchiveOpenCallback> callback = openCallbackSpec;
  openCallbackSpec->Callback = callbackUI;

  CString fullName, prefix, name;
  if (!stream && !stdInMode)
  {
	int fileNamePartStartIndex;
	if (!Common::FileSystem::GetFullPathName(filePath, fullName, fileNamePartStartIndex))
	  return GetLastError();
	prefix = fullName.Left(fileNamePartStartIndex);
	name = fullName.Mid(fileNamePartStartIndex);
	openCallbackSpec->Init(prefix, name);
  }
  else
  {
	openCallbackSpec->SetSubArchiveName(filePath);
  }

  HRESULT hRes = Open(codecs, formatIndices, stdInMode, stream, filePath, callback);
  if(S_OK != hRes)
	return hRes;
  VolumePaths.push_back(prefix + name);
  for (size_t i = 0; i < openCallbackSpec->FileNames.size(); i++)
	VolumePaths.push_back(prefix + openCallbackSpec->FileNames[i]);
  VolumesSize = openCallbackSpec->TotalSize;
  return S_OK;
}

HRESULT CArchiveLink::ReOpen(CCodecs *codecs, const CString &filePath,
	IArchiveOpenCallback *callback)
{
  if (Arcs.size() > 1)
	return E_NOTIMPL;

  if (Arcs.empty())
	return Open2(codecs, std::vector<int>(), false, NULL, filePath, 0);

  CComPtr<IArchiveOpenCallback> openCallbackNew;
  SetCallback(filePath, NULL, callback, openCallbackNew);

  CInFileStream *fileStreamSpec = new CComObject<CInFileStream>;
  fileStreamSpec->FinalConstruct();
  CComPtr<IInStream> stream(fileStreamSpec);
  if (!fileStreamSpec->Open(filePath))
	return GetLastError();
  HRESULT res = GetArchive()->Open(stream, &kMaxCheckStartPosition, callback);
  IsOpen = (res == S_OK);
  return res;
}

}
}